package org.afs.gescol.edt.api.generator;

import java.util.ArrayList;
import java.util.List;

import org.afs.gescol.edt.api.util.MessageKeyUtil;
import org.apache.log4j.Logger;
import org.scub.foundation.framework.base.exception.TechnicalException;
import org.scub.foundation.framework.base.messagesource.MessageSourceUtil;

import solver.Solver;
import solver.constraints.IntConstraintFactory;
import solver.search.strategy.IntStrategyFactory;
import solver.variables.IntVar;
import solver.variables.VariableFactory;

/**
 * Timestable generator implementation.
 * @author Kobla GBENYO (kobla.gbenyo@gmail.com)
 */
public class TimesTableGeneratorImpl implements TimesTableGenerator {

    private MessageSourceUtil messageSourceUtil;

    private static Logger logger = Logger.getLogger(TimesTableGeneratorImpl.class);

    @Override
    public OutputData computeTimestable(InputData input) {
        if (input == null) {
            throw new TechnicalException(messageSourceUtil.get(MessageKeyUtil.ERROR_NULL_COMPTING_INPUT_DATA));
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Input data for generating timestable : " + input.toString());
        }
        StringBuffer loggerText = new StringBuffer();
        // resource & place common availability calculation
        final List<List<Integer>> weeklyCommonAvail = new ArrayList<List<Integer>>();
        for (Integer j = 0; j < input.getDayNumber(); j++) {
            final List<Integer> dailyCommonAvail = new ArrayList<Integer>();
            for (Integer h = 1; h <= input.getDailyTimeTableMax(); h++) {
                if (member(h, input.getRessourceAvailability().get(j)) && member(h, input.getPlaceAvailability().get(j))) {
                    dailyCommonAvail.add(new Integer(h));
                }
            }
            loggerText.append("DAY" + (j + 1) + "=" + dailyCommonAvail).append((j + 1) < input.getDayNumber() ? ";" : ".");
            weeklyCommonAvail.add(dailyCommonAvail);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Place & Ressource common availabilities: " + loggerText.toString());
        }

        // Programmed duration calcultation
        final Solver durationSolver = new Solver("Programmed Duration Solver");
        // duration domain
        final IntVar[] dj = VariableFactory.boundedArray("dj", input.getDayNumber(), 0, input.getDailyTimeSlotMax(), durationSolver);
        // add constraints to the solver
        for (int j = 0; j < input.getDayNumber(); j++) {
            // 1. The programmed time slot duration must not exceed the maximum daily time slot
            durationSolver.post(IntConstraintFactory.arithm(dj[j], "<=", input.getDailyTimeSlotMax()));

            // 2. The programmed time slot duration must not exceed resource & place common availability duration
            durationSolver.post(IntConstraintFactory.arithm(dj[j], "<=", weeklyCommonAvail.get(j).size()));
        }
        // 3. The sum of week durations must equals to hourly volume
        durationSolver.post(IntConstraintFactory.sum(dj, VariableFactory.fixed(input.getWeeklyTimeTableVolume(), durationSolver)));

        // Define the search strategy & Launch the resolution process
        durationSolver.set(IntStrategyFactory.lexico_LB(dj));
        final boolean isSolution = durationSolver.findSolution();

        // getting the result contained the all hourly IF solution is find OR the minimum satisfied hourly
        loggerText = new StringBuffer();
        int totalProgrammedHours = 0;
        final List<List<Integer>> result = new ArrayList<List<Integer>>();
        for (int j = 0; j < input.getDayNumber(); j++) {
            final int commonAvailSize = weeklyCommonAvail.get(j).size();
            final int toIndex = isSolution ? dj[j].getValue()
                : (commonAvailSize <= input.getDailyTimeSlotMax() ? commonAvailSize : input.getDailyTimeSlotMax());
            result.add(weeklyCommonAvail.get(j).subList(0, toIndex));
            totalProgrammedHours += toIndex;
            loggerText.append("DAY" + (j + 1) + "=duration:" + toIndex).append(",hours=" + result.get(j)).append((j + 1) < input.getDayNumber() ? ";" : ".");
        }
        final int remaining = input.getWeeklyTimeTableVolume() - totalProgrammedHours;
        if (logger.isDebugEnabled()) {
            logger.debug("Generated times: " + loggerText.toString() + " Remaining times: " + remaining + ".");
        }

        return new OutputData(result, remaining);
    }

    /**
     * Verify if the integer set IS contains the element h.
     * @param h the element
     * @param is the integer set
     * @return True / False if the element is in IS or not.
     */
    private boolean member(Integer h, List<Integer> is) {
        return is == null ? false : is.contains(h);
    }

    /**
     * Set the value of messageSourceUtil.
     * @param messageSourceUtil the messageSourceUtil to set
     */
    public void setMessageSourceUtil(MessageSourceUtil messageSourceUtil) {
        this.messageSourceUtil = messageSourceUtil;
    }

}
